#pragma once
#include <sqlite3.h>

#include <memory>
#include <unordered_map>

#include "utils/logutils.hpp"

class DBWrapper {
 public:
  static DBWrapper *GetInstance() {
    static DBWrapper db_wrapper;
    return &db_wrapper;
  }

  void Init() {
    this->Open("redcross");
    auto sql =
        "create table if not exists users(\
                        username varchar(20) not null,\
                        phoneNumber varchar(15) not null,\
                        email varchar(50) not null,\
                        primary key (username));\
    create table if not exists donors(donorId integer PRIMARY KEY autoincrement, donorName varchar(50),\
                        phoneNumber varchar(15), address varchar(100),\
                        email varchar(50));\
    create table if not exists donees(doneeId integer PRIMARY KEY autoincrement, doneeName varchar(50),\
                        phoneNumber varchar(15), address varchar(100),\
                        email varchar(50));\
    create table if not exists receipts(receiptID integer PRIMARY KEY autoincrement, donorId int not null,\
                        suppliesName varchar(50) not null,\
                        [type] varchar(20), specifications varchar(20),\
                        units varchar(10),\
                        number int, donateTime varchar(20),\
                        foreign key(donorId) references donors(donorId));\
    create table if not exists distributions(distributionId integer PRIMARY KEY autoincrement, doneeId int not null,\
                        suppliesName varchar(50) not null,\
                        [type] varchar(20), specifications varchar(20),\
                        units varchar(10),\
                        number int, distributeTime varchar(20),\
                        foreign key(doneeId) references donees(doneeId));\
    ";
    // old schema

    char *err = nullptr;
    auto ret = sqlite3_exec(dbHandler_, sql, nullptr, nullptr, &err);
    hassert(ret == 0, err);
  }

  void Open(const std::string &db) {
    auto db_file = base_path_ + db + ".db";
    auto ret = sqlite3_open(db_file.c_str(), &dbHandler_);
    hassert(ret == 0, sqlite3_errmsg(dbHandler_));
  }

  bool Excute(const std::string sql) {
    std::cout << sql << std::endl;
    char *err = nullptr;
    // const std::lock_guard<std::mutex> lock(db_mutex_);
    auto ret = sqlite3_exec(dbHandler_, sql.c_str(), nullptr, nullptr, &err);
    hassert(ret == 0, err);
    return ret == 0;
  }

  bool Insert(const std::string &table,
              const std::vector<std::string> &values) {
    std::string sql = "insert into " + table + " values(";
    for (size_t i = 0; i < values.size(); ++i) {
      sql += values[i] + (i == values.size() - 1 ? ");" : ", ");
    }
    return Excute(sql);
  }

  std::unique_ptr<std::vector<std::vector<std::string>>> Select(
      const std::string &sql) {
    // std::cout << sql << std::endl;
    char *err = nullptr;
    // char **table;
    // int nRow, nCol;
    // auto result = std::make_unique<std::vector<std::vector<std::string>>>();
    // auto ret =
    //     sqlite3_get_table(dbHandler_, sql.c_str(), &table, &nRow, &nCol,
    //     &err);
    // hassert(ret == 0, err);
    // int nIndex = nCol;
    // for (int i = 0; i < nRow; ++i) {
    //   result->push_back(std::vector<std::string>());
    //   for (int j = 0; j < nCol; ++j, ++nIndex) {
    //     result->rbegin()->push_back(table[nIndex] ? table[nIndex] : "");
    //   }
    // }

    auto result = std::make_unique<std::vector<std::vector<std::string>>>();
    auto ret = sqlite3_exec(
        dbHandler_, sql.c_str(),
        [](void *data, int argc, char **argv, char **col) {
          auto result = (std::vector<std::vector<std::string>> *)data;
          result->push_back(std::vector<std::string>());
          for (int i = 0; i < argc; ++i) {
            result->rbegin()->push_back(argv[i] ? argv[i] : "");
          }
          return 0;
        },
        result.get(), &err);
    hassert(ret == 0, err);
    return result;
  }

  void Iterate(std::string table) {
    auto sql = "select * from " + table + ";";
    std::cout << sql << std::endl;
    std::cout << "--- " + table + " ---" << std::endl;
    char *err = nullptr;
    bool first = true;
    auto ret = sqlite3_exec(
        dbHandler_, sql.c_str(),
        [](void *first, int argc, char **argv, char **col) {
          if (*(bool *)first) {
            for (int i = 0; i < argc; ++i) {
              printf("%-15s", col[i]);
            }
            printf("\n");
          }
          for (int i = 0; i < argc; ++i) {
            printf("%-15s", argv[i]);
          }
          printf("\n");
          *(bool *)first = false;
          return 0;
        },
        &first, nullptr);
    std::cout << std::endl;
    hassert(ret == 0, err);
  }

 private:
  DBWrapper(){};

  sqlite3 *dbHandler_;

  const std::string base_path_ = "/tmp/db_";

  // std::mutex db_mutex_;

  // static int NilCallback_(void *data, int argc, char **argv, char **col) {
  // return 0; }
};